using System;
using System.Buffers;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using eLibrary.Constant;
using eLibrary.Hash;
using ePipe;
using eTask.Extensions;
using Letter.IO.Binary;

namespace Letter.Net.Kcp;

abstract class AUdpConnection : AConnection, IUdpSender
{
    public AUdpConnection(KcpTransportOptions options, Func<AConnection> connectionFactory, Func<IPipe<byte>> channelProvider)
    {
        this.options = options;
        this.connectionFactory = connectionFactory;
        this.channelProvider = channelProvider;
    }

    private KcpTransportOptions options;
    private Func<AConnection> connectionFactory;
    private int lockState = Booleans.FALSE;
    private Func<IPipe<byte>> channelProvider;
    

    private Random random = new Random();

    protected int Seed
    {
        get { return this.options.seed; }
    }
    
    protected KcpTransportOptions Options
    {
        get { return this.options; }
    }

    protected Func<AConnection> ConnectionFactory
    {
        get { return this.connectionFactory; }
    }

    protected Func<IPipe<byte>> ChannelProvider
    {
        get { return this.channelProvider; }
    }

    private IPipeReader<byte> Reader
    {
        get { return base.Transport.Reader; }
    }

    private IPipeWriter<byte> Writer
    {
        get { return base.Transport.Writer; }
    }

    protected abstract KcpSession GetTransport(EndPoint remoteEP);

    protected int GetRandomNumber()
    {
        return this.random.Next(0, int.MaxValue);
    }

    protected uint CreateConv(int connectId)
    {
        var s_span = (stackalloc byte[12]);
        var span1 = s_span.Slice(0, 4);
        var span2 = s_span.Slice(4, 8);
        span1.FWriteInt32BE(connectId);
        span2.FWriteInt64BE(connectId + this.Seed);
        return HashGenerator.Create(s_span);
    }

    public override void OnActive()
    {
        _ = this.StartReadAsync();
    }

    private async Task StartReadAsync()
    {
        var input = base.Transport.Reader;
        while(true)
        {
            var result = await input.ReadAsync();
            if(result.IsCanceled || result.IsCompleted)
            {
                break;
            }

            var buf = result.Buffer;
            this.OnMessage((EndPoint) input.Token, input, ref buf);
            input.AdvanceTo(result.Buffer.End);
            input.Flush();
        }
    }

    protected abstract void OnMessage(EndPoint remoteEP, IPipeReader<byte> reader, ref ReadOnlySequence<byte> buf);
    
    public override void OnException(Exception ex)
    {
    }

    protected void RcvKcpMessage(EndPoint remoteEP, ref BytesReader bytesReader)
    {
        if (bytesReader.ReadableLength < 4)
        {
            return;
        }

        var transport = this.GetTransport(remoteEP);
        if (transport == null)
        {
            return;
        }
        
        var connectId = bytesReader.ReadInt32BE();
        var conv = this.CreateConv(connectId);
        if (transport.ConnectId != connectId || transport.Conv != conv)
        {
            return;
        }

        var readableLength = (int) bytesReader.ReadableLength;
        var buf = bytesReader.ReadBuffer(readableLength);
        transport.OnRcvUdpMessage(ref buf);
    }

    public void SendMessage(EndPoint remote, MessageType type, int connectId, ref ReadOnlySequence<byte> buf)
    {
        if (!this.Active)
        {
            return;
        }

        if (buf.IsEmpty)
        {
            return;
        }

        while (Interlocked.CompareExchange(ref this.lockState, Booleans.TRUE, Booleans.FALSE) != Booleans.FALSE)
        {
        }

        var bytesWriter = this.Writer.ToBytesWriter();
        bytesWriter.WriteUInt8((byte)type);
        bytesWriter.WriteInt32BE(connectId);
        bytesWriter.WriteSequence(ref buf);
        this.Writer.FlushAsync().Ignore();
        
        Interlocked.Exchange(ref this.lockState, Booleans.FALSE);
    }
    
    protected void SendSpan(EndPoint remote, MessageType type, in ReadOnlySpan<byte> buf)
    {
        if (!this.Active)
        {
            return;
        }

        if (buf.IsEmpty)
        {
            return;
        }

        while (Interlocked.CompareExchange(ref this.lockState, Booleans.TRUE, Booleans.FALSE) != Booleans.FALSE)
        {
        }

        var bytesWriter = this.Writer.ToBytesWriter();
        bytesWriter.WriteUInt8((byte)type);
        bytesWriter.WriteSpan(buf);
        this.Writer.FlushAsync().Ignore();
        
        Interlocked.Exchange(ref this.lockState, Booleans.FALSE);
    }

    public override void OnInactive()
    {
        throw new NotImplementedException();
    }
}